/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.jfoenix.skins;

import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXSlider.IndicatorPosition;
import com.sun.javafx.scene.control.skin.SliderSkin;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.util.Duration;

/**
 * <h1>Material Design Slider Skin</h1>
 * <p>
 * rework of JFXSliderSkin by extending Java SliderSkin
 * this solves padding and resizing issues
 *
 * @author Shadi Shaheen
 * @version 1.0
 * @since 2016-03-09
 */
public class JFXSliderSkin extends SliderSkin {


    private Paint thumbColor = Color.valueOf("#0F9D58"), trackColor = Color.valueOf("#CCCCCC");

    private Text sliderValue;
    private StackPane coloredTrack;
    private StackPane thumb;
    private StackPane track;
    private StackPane animatedThumb;

    private Timeline timeline;

    private double indicatorRotation;
    private double horizontalRotation;
    private double shifting;
    private boolean isValid = false;


    public JFXSliderSkin(JFXSlider slider) {
        super(slider);

        track = (StackPane) getSkinnable().lookup(".track");
        thumb = (StackPane) getSkinnable().lookup(".thumb");

        track.setBackground(new Background(new BackgroundFill(trackColor, new CornerRadii(5), Insets.EMPTY)));
        thumb.setBackground(new Background(new BackgroundFill(thumbColor, new CornerRadii(20), Insets.EMPTY)));
        track.setPrefHeight(2);
        track.setPrefWidth(2);

        coloredTrack = new StackPane();
        coloredTrack.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
            BackgroundFill trackBackgroundFill = track.getBackground().getFills().get(0);
            return new Background(new BackgroundFill(thumb.getBackground().getFills().get(0).getFill(),
                trackBackgroundFill.getRadii(),
                trackBackgroundFill.getInsets()));
        }, track.backgroundProperty(), thumb.backgroundProperty()));
        coloredTrack.setMouseTransparent(true);

        sliderValue = new Text();
        sliderValue.setStroke(Color.WHITE);
        sliderValue.setFont(new Font(10));
        sliderValue.getStyleClass().setAll("slider-value");

        animatedThumb = new StackPane();
        animatedThumb.getStyleClass().add("animated-thumb");
        animatedThumb.getChildren().add(sliderValue);
        animatedThumb.setMouseTransparent(true);
        animatedThumb.setPrefSize(30, 30);
        animatedThumb.setBackground(new Background(new BackgroundFill(thumbColor,
            new CornerRadii(50, 50, 50, 0, true),
            null)));
        animatedThumb.setScaleX(0);
        animatedThumb.setScaleY(0);

        getChildren().add(getChildren().indexOf(thumb), coloredTrack);
        getChildren().add(getChildren().indexOf(thumb), animatedThumb);

        registerChangeListener(slider.valueFactoryProperty(), "VALUE_FACTORY");

        initListeners();
    }

    @Override
    protected void handleControlPropertyChanged(String p) {
        super.handleControlPropertyChanged(p);
        if ("VALUE_FACTORY".equals(p)) {
            refreshSliderValueBinding();
        }
    }

    private void refreshSliderValueBinding() {
        sliderValue.textProperty().unbind();
        if (((JFXSlider) getSkinnable()).getValueFactory() != null) {
            sliderValue.textProperty()
                .bind(((JFXSlider) getSkinnable()).getValueFactory().call((JFXSlider) getSkinnable()));
        } else {
            sliderValue.textProperty().bind(Bindings.createStringBinding(() -> {
                if (getSkinnable().getLabelFormatter() != null) {
                    return getSkinnable().getLabelFormatter().toString(getSkinnable().getValue());
                } else {
                    return Math.round(getSkinnable().getValue()) + "";
                }
            }, getSkinnable().valueProperty()));

        }
    }

    @Override
    protected void layoutChildren(double x, double y, double w, double h) {
        super.layoutChildren(x, y, w, h);

        if (!isValid) {
            initializeVariables();
            initAnimation(getSkinnable().getOrientation());
            isValid = true;
        }

        double prefWidth = animatedThumb.prefWidth(-1);
        animatedThumb.resize(prefWidth, animatedThumb.prefHeight(prefWidth));

        boolean horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
        double width, height, layoutX, layoutY;
        if (horizontal) {
            width = thumb.getLayoutX() - snappedLeftInset();
            height = track.getHeight();
            layoutX = track.getLayoutX();
            layoutY = track.getLayoutY();
            animatedThumb.setLayoutX(thumb.getLayoutX() + thumb.getWidth() / 2 - animatedThumb.getWidth() / 2);
        } else {
            height = track.getLayoutBounds().getMaxY() + track.getLayoutY() - thumb.getLayoutY() - snappedBottomInset();
            width = track.getWidth();
            layoutX = track.getLayoutX();
            layoutY = thumb.getLayoutY();
            animatedThumb.setLayoutY(thumb.getLayoutY() + thumb.getHeight() / 2 - animatedThumb.getHeight() / 2);
        }

        coloredTrack.resizeRelocate(layoutX, layoutY, width, height);


    }

    private boolean internalChange = false;

    private void initializeVariables() {

        shifting = 30 + thumb.getWidth();

        if (getSkinnable().getOrientation() != Orientation.HORIZONTAL) {
            horizontalRotation = -90;
        }

        if (((JFXSlider) getSkinnable()).getIndicatorPosition() != IndicatorPosition.LEFT) {
            indicatorRotation = 180;
            shifting = -shifting;
        }

        final double rotationAngle = 45;
        sliderValue.setRotate(rotationAngle + indicatorRotation + 3 * horizontalRotation);

        animatedThumb.setRotate(-rotationAngle + indicatorRotation + horizontalRotation);
        thumb.backgroundProperty().addListener((o, oldVal, newVal) -> {
            if (animatedThumb.getBackground() != null) {
                animatedThumb.setBackground(new Background(new BackgroundFill(newVal.getFills().get(0).getFill(),
                    animatedThumb.getBackground()
                        .getFills()
                        .get(0)
                        .getRadii(),
                    animatedThumb.getBackground()
                        .getFills()
                        .get(0)
                        .getInsets())));
            } else {
                animatedThumb.setBackground(new Background(new BackgroundFill(newVal.getFills().get(0).getFill(),
                    new CornerRadii(50, 50, 50, 0, true),
                    null)));
            }
        });
    }


    private void initListeners() {
        // delegate slider mouse events to track node
        getSkinnable().setOnMousePressed(me -> {
            if (!me.isConsumed()) {
                me.consume();
                track.fireEvent(me);
            }
        });
        getSkinnable().setOnMouseReleased(me -> {
            if (!me.isConsumed()) {
                me.consume();
                track.fireEvent(me);
            }
        });
        getSkinnable().setOnMouseDragged(me -> {
            if (!me.isConsumed()) {
                me.consume();
                track.fireEvent(me);
            }
        });

        // animate value node
        track.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
            timeline.setRate(1);
            timeline.play();
        });
        track.addEventHandler(MouseEvent.MOUSE_RELEASED, (event) -> {
            timeline.setRate(-1);
            timeline.play();
        });
        thumb.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
            timeline.setRate(1);
            timeline.play();
        });
        thumb.addEventHandler(MouseEvent.MOUSE_RELEASED, (event) -> {
            timeline.setRate(-1);
            timeline.play();
        });

        track.backgroundProperty().addListener((o, oldVal, newVal) -> {
            // prevent internal color change
            if (!internalChange && newVal != null) {
                trackColor = newVal.getFills().get(0).getFill();
            }
        });

        thumb.backgroundProperty().addListener((o, oldVal, newVal) -> {
            // prevent internal color change
            if (!internalChange && newVal != null) {
                thumbColor = newVal.getFills().get(0).getFill();
                if (getSkinnable().getValue() == getSkinnable().getMin()) {
                    internalChange = true;
                    thumb.setBackground(new Background(new BackgroundFill(trackColor,
                        new CornerRadii(20),
                        Insets.EMPTY)));
                    internalChange = false;
                }
            }
        });

        refreshSliderValueBinding();

        getSkinnable().valueProperty().addListener((o, oldVal, newVal) -> {
            internalChange = true;
            if (getSkinnable().getMin() == newVal.doubleValue()) {
                thumb.setBackground(new Background(new BackgroundFill(trackColor, new CornerRadii(20), Insets.EMPTY)));
                animatedThumb.pseudoClassStateChanged(PseudoClass.getPseudoClass("min"), true);
            } else if (oldVal.doubleValue() == getSkinnable().getMin()) {
                thumb.setBackground(new Background(new BackgroundFill(thumbColor, new CornerRadii(20), Insets.EMPTY)));
                animatedThumb.pseudoClassStateChanged(PseudoClass.getPseudoClass("min"), false);
            }
            internalChange = false;
        });


        getSkinnable().orientationProperty().addListener((o, oldVal, newVal) -> initAnimation(newVal));
        animatedThumb.layoutBoundsProperty()
            .addListener((o, oldVal, newVal) -> initAnimation(getSkinnable().getOrientation()));
    }


    private void initAnimation(Orientation orientation) {
        double thumbPos, thumbNewPos;
        DoubleProperty layoutProperty;

        if (orientation == Orientation.HORIZONTAL) {
            if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) {
                thumbPos = thumb.getLayoutY() - thumb.getHeight();
                thumbNewPos = thumbPos - shifting;
            } else {
                thumbPos = thumb.getLayoutY() - animatedThumb.getHeight() / 2;
                thumbNewPos = thumb.getLayoutY() - animatedThumb.getHeight() - thumb.getHeight();
            }
            layoutProperty = animatedThumb.translateYProperty();
        } else {
            if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) {
                thumbPos = thumb.getLayoutX() - thumb.getWidth();
                thumbNewPos = thumbPos - shifting;
            } else {
                thumbPos = thumb.getLayoutX() - animatedThumb.getWidth() / 2;
                thumbNewPos = thumb.getLayoutX() - animatedThumb.getWidth() - thumb.getWidth();
            }
            layoutProperty = animatedThumb.translateXProperty();
        }


        timeline = new Timeline(
            new KeyFrame(
                Duration.ZERO,
                new KeyValue(animatedThumb.scaleXProperty(), 0, Interpolator.EASE_BOTH),
                new KeyValue(animatedThumb.scaleYProperty(), 0, Interpolator.EASE_BOTH),
                new KeyValue(layoutProperty, thumbPos, Interpolator.EASE_BOTH)),
            new KeyFrame(
                Duration.seconds(0.2),
                new KeyValue(animatedThumb.scaleXProperty(), 1, Interpolator.EASE_BOTH),
                new KeyValue(animatedThumb.scaleYProperty(), 1, Interpolator.EASE_BOTH),
                new KeyValue(layoutProperty, thumbNewPos, Interpolator.EASE_BOTH)));
    }

}
